package com.chisheng.caa.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.chisheng.caa.config.CaaProperties;
import com.chisheng.caa.exception.ServiceException;
import com.chisheng.caa.mapper.CourseMapper;
import com.chisheng.caa.mapper.MajorMapper;
import com.chisheng.caa.pojo.ArrangeCourseSetting;
import com.chisheng.caa.pojo.DuplicateStrategy;
import com.chisheng.caa.pojo.Semester;
import com.chisheng.caa.pojo.dto.ClassScheduleDTO;
import com.chisheng.caa.pojo.dto.CourseSchedulingDTO;
import com.chisheng.caa.pojo.dto.ExcelSheetDTO;
import com.chisheng.caa.pojo.dto.GradeDTO;
import com.chisheng.caa.pojo.entity.Course;
import com.chisheng.caa.pojo.entity.Major;
import com.chisheng.caa.service.AbstractArrangeCourseService;
import com.chisheng.caa.service.FileService;
import com.chisheng.caa.service.LocalMultipartFile;
import com.chisheng.caa.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.chisheng.caa.pojo.LoadingStrategy.OVERWRITE;
import static com.chisheng.caa.service.FileService.UPLOAD_FORM_PARAM_NAME;
import static com.chisheng.caa.util.EasyExcelUtil.EXCEL_MIME;
import static com.chisheng.caa.util.EasyExcelUtil.DEFAULT_EXT;

@Service
public class ArrangeCourseServiceImpl extends AbstractArrangeCourseService {

    @Resource
    private CaaProperties properties;

    @Resource
    private ArrangeCourseSetting defaultSetting;

    @Resource
    private MajorMapper majorMapper;

    @Resource
    private CourseMapper courseMapper;

    @Resource
    private FileService fileService;

    @Autowired
    public ArrangeCourseServiceImpl(ExpiryMapCacheUtil cacheUtil) {
        super(cacheUtil);
    }

    @Override
    public String open() {
        ArrangeCourseState state = new ArrangeCourseState();
        state.setData(Map.of());
        state.setSetting(defaultSetting);
        return cacheUtil.put(state);
    }

    @Override
    public void setSetting(String accessKey, ArrangeCourseSetting setting) {
        ArrangeCourseState state = cacheUtil.checkAndGet(accessKey);

        try {
            // 刷新排课课表
            state.setSetting(setting);
            Map<String, CourseSchedulingDTO> data = state.getData();
            Collection<CourseSchedulingDTO> courseList = generateCourseSchedulingList(state.getSetting());
            DuplicateStrategy strategy = setting.getDuplicateStrategy();
            MapUtil.putAll(data, courseList, OVERWRITE, strategy);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }

    @Override
    public String export(String accessKey) {
        ArrangeCourseState state = cacheUtil.checkAndGet(accessKey);
        // 转换为导出格式数据
        List<ClassScheduleDTO> scheduleList = state.getData().values()
                .stream().map(this::convert).collect(Collectors.toList());
        // 写入文件
        ExcelSheetDTO sheet = new ExcelSheetDTO(properties.getExporting());
        byte[] bytes = EasyExcelUtil.write(ClassScheduleDTO.class, scheduleList, sheet);
        String contentType = EXCEL_MIME.get(DEFAULT_EXT);
        String originalName = FileUtil.genName(DEFAULT_EXT);
        MultipartFile file = new LocalMultipartFile(UPLOAD_FORM_PARAM_NAME, originalName, contentType, bytes);
        return fileService.upload(file);
    }

    /**
     * 获取推荐的排课课程列表
     */
    private Collection<CourseSchedulingDTO> generateCourseSchedulingList(ArrangeCourseSetting setting) {
        return generateSuggestionCourseList(setting).stream().map(this::autofill)
                .collect(Collectors.toList());
    }

    /**
     * 生成推荐课程列表。根据课程所属年级、专业和学期进行匹配。
     */
    private List<Course> generateSuggestionCourseList(ArrangeCourseSetting setting) {
        GradeDTO grade = setting.getGrade();
        if (grade == null || grade.isEmpty()) return List.of();
        String majorId = majorMapper.selectIdByMajorName(grade.getMajorName());
        Semester semester = Objects.requireNonNull(setting.getSemester());
        LambdaQueryWrapper<Course> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Course::getEnrollmentYear, grade.getEnrollmentYear());
        wrapper.eq(Course::getMajorId, majorId);
        wrapper.eq(Course::getStudyingTerm, semester.getValue());
        return courseMapper.selectList(wrapper);
    }

    /**
     * 自动填值。将 ImportedCurriculum 实例映射为 ArrangementCurriculumDTO 实例。
     * 映射值中能在导入课程中所找到的自动映射到排课课程，其余要么为空，要么从配置中读取。
     */
    private CourseSchedulingDTO autofill(Course course) {
        CourseSchedulingDTO courseDTO = new CourseSchedulingDTO();
        Major major = majorMapper.selectById(course.getMajorId());

        // 从导入课程信息中读取
        // CourseCode, CourseName, CourseNature, TheoreticalCredit,
        // PracticalCredit, TheoreticalPeriod, PracticalPeriod
        BeanUtil.copyProperties(course, courseDTO);

        // 从默认配置中读取
        copyPropertiesFromConfig(courseDTO);
        courseDTO.setGradeSuggestion(course.getEnrollmentYear());
        courseDTO.setOpeningClass(String.format("%d%s", course.getEnrollmentYear(), major.getMajorName()));
        courseDTO.setOpeningCollege(course.getOpeningCollege());
        courseDTO.setTeachingCollege(course.getTeachingCollege());
        courseDTO.setNumLaboratories(0);
        courseDTO.setNumMedias(0);
        courseDTO.setNumMachineRooms(0);
        return courseDTO;
    }

    /**
     * 从默认配置中初始化属性值
     */
    private void copyPropertiesFromConfig(CourseSchedulingDTO courseDTO) {
        courseDTO.setSchoolRunningLevel(properties.getExporting().getDefaultSchoolRunningLevel());
        courseDTO.setStartingTeachingWeeks(properties.getExporting().getDefaultStartingTeachingWeek());
        courseDTO.setTerminalTeachingWeeks(properties.getExporting().getDefaultTerminalTeachingWeek());
        courseDTO.setIsTeacherEducationCourse(properties.getExporting().getDefaultBooleanValueCn());
        courseDTO.setIsPoliticalCourse(properties.getExporting().getDefaultBooleanValueCn());
        courseDTO.setIsLawCourse(properties.getExporting().getDefaultBooleanValueCn());
        courseDTO.setIsNationalCourse(properties.getExporting().getDefaultBooleanValueCn());
        courseDTO.setIsForeignCourse(properties.getExporting().getDefaultBooleanValueCn());
        courseDTO.setIsAestheticCourse(properties.getExporting().getDefaultBooleanValueCn());
        courseDTO.setIsLabourCourse(properties.getExporting().getDefaultBooleanValueCn());
    }

    /**
     * 将数据转换为 Excel 输出格式
     */
    private ClassScheduleDTO convert(CourseSchedulingDTO source) {
        ClassScheduleDTO dest = new ClassScheduleDTO();
        BeanUtil.copyProperties(source, dest);
        dest.setCredit(String.format("%.1f+%.1f", source.getTheoreticalCredit(), source.getPracticalCredit()));
        Integer ttw = Objects.requireNonNullElse(source.getTerminalTeachingWeeks(), 0);
        Integer stw = Objects.requireNonNullElse(source.getStartingTeachingWeeks(), 0);
        int tws = ttw - stw + 1;
        Integer tp = Objects.requireNonNullElse(source.getTheoreticalPeriod(), 0);
        Integer pp = Objects.requireNonNullElse(source.getPracticalPeriod(), 0);
        dest.setWeekPeriods(String.format("%d-%d", tp / tws, pp / tws));
        dest.setNumTeachingWeeks(String.format("%d-%d", stw, ttw));
        dest.setTotalPeriods(tp + pp);
        return dest;
    }

}
